package com.google.code.mochaccino.framework.util;
/*
 * Copyright 2012 Claude Houle claude.houle@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.io.IOUtils;

/**
 * A Properties Class that sort it's keys when writing them down to disk. Easier Diff :)
 *
 *
 */
public class SortedProperties {
	/** A table of hex digits */
	private static final char[] hexDigit = { '0',
	                                         '1',
	                                         '2',
	                                         '3',
	                                         '4',
	                                         '5',
	                                         '6',
	                                         '7',
	                                         '8',
	                                         '9',
	                                         'A',
	                                         'B',
	                                         'C',
	                                         'D',
	                                         'E',
	                                         'F' };

	private Map<String,String> properties = Collections.synchronizedMap( new TreeMap<String,String>() );

	public SortedProperties() {
	}

	public SortedProperties( InputStream inStream ) throws IOException {
		try {
			Properties resolvedProperties = new Properties();
			resolvedProperties.load( inStream );
			loadFromProperties( resolvedProperties );
		} finally {
			IOUtils.closeQuietly( inStream );
		}
	}

	private void loadFromProperties( Properties resolvedProperties ) {
		for ( Object key : resolvedProperties.keySet() ) {
			if ( key instanceof String ) {
				String k = (String) key;
				String v = resolvedProperties.getProperty( k );
				properties.put( k, v );
			}
		}
	}

	public SortedProperties( Properties resolvedProperties ) {
		loadFromProperties( resolvedProperties );
	}

	public String getProperty( String property ) {
		return properties.get( property );
	}

	public String getProperty( String property, String defaultValue ) {
		String value = properties.get( property );
		if ( value == null ) {
			return defaultValue;
		}
		return value;
	}

	public Collection<String> getPropertyNames() {
		return properties.keySet();
	}

	public void setProperty( String property, String value ) {
		properties.put( property, value );
	}

	public void store( File output ) throws IOException {
		BufferedWriter out = new BufferedWriter( new FileWriter( output ) );
		for ( Entry<String,String> entry : properties.entrySet() ) {
			out.write( normalizeKey( entry.getKey() ) );
			out.write( "=" );
			out.write( normalizeValue( entry.getValue() ) );
			out.newLine();
		}
		IOUtils.closeQuietly( out );
	}

	public static String normalizeKey( String key ) {
		return saveConvert( key, true, false );
	}

	private static String saveConvert( String theString, boolean escapeSpace, boolean escapeUnicode ) {
		int len = theString.length();
		int bufLen = len * 2;
		if ( bufLen < 0 ) {
			bufLen = Integer.MAX_VALUE;
		}
		StringBuffer outBuffer = new StringBuffer( bufLen );
		for ( int x = 0; x < len; x++ ) {
			char aChar = theString.charAt( x );
			// Handle common case first, selecting largest block that
			// avoids the specials below
			if ( (aChar > 61) && (aChar < 127) ) {
				if ( aChar == '\\' ) {
					outBuffer.append( '\\' );
					outBuffer.append( '\\' );
					continue;
				}
				outBuffer.append( aChar );
				continue;
			}
			switch ( aChar ) {
				case ' ':
					if ( (x == 0) || escapeSpace ) {
						outBuffer.append( '\\' );
					}
					outBuffer.append( ' ' );
					break;
				case '\t':
					outBuffer.append( '\\' );
					outBuffer.append( 't' );
					break;
				case '\n':
					outBuffer.append( '\\' );
					outBuffer.append( 'n' );
					break;
				case '\r':
					outBuffer.append( '\\' );
					outBuffer.append( 'r' );
					break;
				case '\f':
					outBuffer.append( '\\' );
					outBuffer.append( 'f' );
					break;
				case '=': // Fall through
				case ':': // Fall through
				case '#': // Fall through
				case '!':
					outBuffer.append( '\\' );
					outBuffer.append( aChar );
					break;
				default:
					if ( ((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) {
						outBuffer.append( '\\' );
						outBuffer.append( 'u' );
						outBuffer.append( toHex( (aChar >> 12) & 0xF ) );
						outBuffer.append( toHex( (aChar >> 8) & 0xF ) );
						outBuffer.append( toHex( (aChar >> 4) & 0xF ) );
						outBuffer.append( toHex( aChar & 0xF ) );
					} else {
						outBuffer.append( aChar );
					}
			}
		}
		return outBuffer.toString();
	}

	private static char toHex( int nibble ) {
		return hexDigit[(nibble & 0xF)];
	}

	public static String normalizeValue( String key ) {
		return saveConvert( key, false, false );
	}
}